
package Interprete;

import Interprete.Celda.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.List;
import java.util.Stack;

import Traductor.CInstruccion;
import Traductor.CInstruccionIO;
import Traductor.Tipo;
import Traductor.TipoInstruccion;

/**
 *
 */
public class MaquinaP {

	private List<CInstruccion> memoriaInstrucciones;
	private Celda[] memoriaDatos;
	private Stack<Celda> pila;
	private int halt;
	//Celda para realizar las operaciones, 
	private Celda celdaAux;
	private int contadorPrograma;
	private int cimaPila;
	private boolean modoTraza;
	private String bufferEntrada;
	private int heapCounter;
	private static int TAM_STAT = 1000;
	private static int TAM_MEM = 9999;

	
	public MaquinaP(List<CInstruccion> memoriaInstrucciones, boolean modoTraza){
		this.memoriaInstrucciones = memoriaInstrucciones;
		this.pila = new Stack<Celda>();
		this.memoriaDatos = new Celda[TAM_MEM];
		this.contadorPrograma = 0;
		this.cimaPila = -1;
		this.halt = 0;
		this.modoTraza = modoTraza;
		this.heapCounter = TAM_STAT;
	}
	
	public void run() throws IOException{
		while (halt == 0){		
			if(modoTraza){
				muestraEstado();
				System.out.println("\nPulse Enter para continuar ejecucion. Siguiente instruccion: " + memoriaInstrucciones.get(contadorPrograma).toString());
				Reader entrada = new InputStreamReader(System.in);
				BufferedReader bufferIn = new BufferedReader(entrada);
				bufferIn.readLine();
			}
			traduceInstruccion(memoriaInstrucciones.get(contadorPrograma));
			contadorPrograma++;
		}
	}
	
	public void traduceInstruccion(CInstruccion instruccion) throws IOException{	    
		
	    if(instruccion.getInstruccion().equals(TipoInstruccion.inMem)){
	    	memoriaDatos[instruccion.getDir()] = new Celda(instruccion.getTipo(),0);
	    }
	       
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.apilaInt) || instruccion.getInstruccion().equals(TipoInstruccion.apilaReal) ){
	    	pila.push(new Celda(instruccion.getTipo(),instruccion.getValor()));
	    	cimaPila++;
	    }
	    
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.apilaDir)){
	    	pila.push(new Celda(memoriaDatos[instruccion.getDir()]));
	    	cimaPila++;
	    }
	    
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.desapilaDir)){
	    	celdaAux = pila.pop();
	    	cimaPila--;
	    	memoriaDatos[instruccion.getDir()] = new Celda(memoriaDatos[instruccion.getDir()].getTipo(), celdaAux.getValor());
	    }
	    
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.castInt)){	    	
	    	pila.peek().setTipo(Tipo.INT);
	    	pila.peek().setValor((int)pila.peek().getValor());
	    }
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.castReal)){
	    	pila.peek().setTipo(Tipo.REAL);
	    	pila.peek().setValor((double)pila.peek().getValor());
	    }
	    
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.menor)){
	    	celdaAux = pila.pop();
	    	cimaPila--;
	    	if(pila.peek().getValor() < celdaAux.getValor())
	    		pila.peek().setValor(1);
	    	else pila.peek().setValor(0);

	    }
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.mayor)){
	    	celdaAux = pila.pop();
	    	cimaPila--;
	    	if(pila.peek().getValor() > celdaAux.getValor())
	    		pila.peek().setValor(1);
	    	else pila.peek().setValor(0);
	    }
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.menorIgual)){
	    	celdaAux = pila.pop();
	    	cimaPila--;
	    	if(pila.peek().getValor() <= celdaAux.getValor())
	    		pila.peek().setValor(1);
	    	else pila.peek().setValor(0);
	    }
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.mayorIgual)){
	    	celdaAux = pila.pop();
	    	cimaPila--;
	    	if(pila.peek().getValor() >= celdaAux.getValor())
	    		pila.peek().setValor(1);
	    	else pila.peek().setValor(0);
	    }
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.distinto)){
	    	celdaAux = pila.pop();
	    	cimaPila--;
	    	if(pila.peek().getValor() != celdaAux.getValor())
	    		pila.peek().setValor(1);
	    	else pila.peek().setValor(0);
	    }
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.igual)){
	    	celdaAux = pila.pop();
	    	cimaPila--;
	    	if(pila.peek().getValor() == celdaAux.getValor())
	    		pila.peek().setValor(1);
	    	else pila.peek().setValor(0);
	    }
	    
	    else if (instruccion.getInstruccion().equals(TipoInstruccion.menosUnario)){
	    	pila.peek().setValor(-pila.peek().getValor());
	    }
	    else if (instruccion.getInstruccion().equals(TipoInstruccion.suma)){
	    	celdaAux = pila.pop();
	    	cimaPila--;
	    	if(pila.peek().getTipo() == Tipo.REAL || celdaAux.getTipo() == Tipo.REAL)
	    		pila.peek().setTipo(Tipo.REAL);
	    	pila.peek().setValor(pila.peek().getValor() + celdaAux.getValor());
	    }	
	    
	    else if (instruccion.getInstruccion().equals(TipoInstruccion.resta)){
	    	celdaAux = pila.pop();
	    	cimaPila--;
	    	if(pila.peek().getTipo() == Tipo.REAL || celdaAux.getTipo() == Tipo.REAL)
	    		pila.peek().setTipo(Tipo.REAL);
	    	pila.peek().setValor(pila.peek().getValor() - celdaAux.getValor());
	    }
	    else if (instruccion.getInstruccion().equals(TipoInstruccion.multiplica)){
	    	celdaAux = pila.pop();
	    	cimaPila--;
	    	if(pila.peek().getTipo() == Tipo.REAL || celdaAux.getTipo() == Tipo.REAL)
	    		pila.peek().setTipo(Tipo.REAL);
	    	pila.peek().setValor(pila.peek().getValor() * celdaAux.getValor());
	    }
	    else if (instruccion.getInstruccion().equals(TipoInstruccion.resta)){
	    	celdaAux = pila.pop();
	    	cimaPila--;
	    	if(pila.peek().getTipo() == Tipo.REAL || celdaAux.getTipo() == Tipo.REAL)
	    		pila.peek().setTipo(Tipo.REAL);
	    	pila.peek().setValor(pila.peek().getValor() - celdaAux.getValor());
	    }
	    
	    else if (instruccion.getInstruccion().equals(TipoInstruccion.or)){
	    	celdaAux = pila.pop();
	    	cimaPila--;
	    	if(pila.peek().getValor() != 0 || celdaAux.getValor() != 0)
	    		pila.peek().setValor(1);
	    	else pila.peek().setValor(0);
	    }
	    
	    else if (instruccion.getInstruccion().equals(TipoInstruccion.and)){
	    	celdaAux = pila.pop();
	    	cimaPila--;
	    	if(pila.peek().getValor() == 0 || celdaAux.getValor() == 0)
	    		pila.peek().setValor(0);
	    	else pila.peek().setValor(1);
	    }
	    else if (instruccion.getInstruccion().equals(TipoInstruccion.modulo)){
	    	celdaAux = pila.pop();
	    	cimaPila--;
	    	if (celdaAux.getValor() <= 0) throw new ArithmeticException("Error en ejecucion, Segundo operando de % no positivo");
	    	pila.peek().setValor(pila.peek().getValor() % celdaAux.getValor());
	    }
	    else if (instruccion.getInstruccion().equals(TipoInstruccion.divide)){
	    	celdaAux = pila.pop();
	    	cimaPila--;
	    	if (celdaAux.getValor() == 0) throw new ArithmeticException("Error en ejecucion, Division por cero");
	    	if(pila.peek().getTipo() == Tipo.REAL || celdaAux.getTipo() == Tipo.REAL){
	    		pila.peek().setTipo(Tipo.REAL);
	    		pila.peek().setValor(pila.peek().getValor() / celdaAux.getValor());
	    	}
	    	else pila.peek().setValor((int) (pila.peek().getValor() / celdaAux.getValor()));
	    }
	    
	    
	    else if (instruccion.getInstruccion().equals(TipoInstruccion.not)){
	    	if (pila.peek().getValor()==0) pila.peek().setValor(1);
	    	else pila.peek().setValor(0);
	    }
	    
	    else if (instruccion.getInstruccion().equals(TipoInstruccion.escribir)){
	    	if(pila.peek().getTipo() == Tipo.INT) System.out.println((int) pila.peek().getValor());
	    	else System.out.println(pila.peek().getValor());
	    }
	    
	    else if (instruccion.getInstruccion().equals(TipoInstruccion.leer)){
	    	BufferedReader bufferIn = new BufferedReader(new InputStreamReader(System.in));
	    	System.out.print(">> ");
	    	bufferEntrada = bufferIn.readLine();
	    	if (pila.peek().getTipo() == Tipo.REAL){
	    		try{
	    			float valor = Float.valueOf(bufferEntrada);
	    			pila.peek().setValor(valor);
	    		}
	    		catch (NumberFormatException e){
	    			System.err.println("ERROR: la entrada debe ser de tipo real");
	    			System.exit(-1);
	    		}
	    	}
	    	else{
	    		try{
	    			int valor = Integer.valueOf(bufferEntrada);
	    			pila.peek().setValor(valor);
	    		}
	    		catch (NumberFormatException e){
	    			System.err.println("ERROR: la entrada debe ser de tipo int");
	    			System.exit(-1);
	    		}
	    	}
	    }
	    
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.stop)){
	    	halt = 1;
	    }
	    
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.apila_ind)){
	    	celdaAux = new Celda(memoriaDatos[(int)pila.peek().getValor()]);
	    	pila.pop();
	    	pila.push(celdaAux);
	    }
	    
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.desapila_ind)){
	    	celdaAux = pila.pop();
	    	memoriaDatos[(int)pila.peek().getValor()] = celdaAux;
	    	pila.pop();
	    }
	    
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.Mueve)){
	    	for (int i = 0; i < (int)instruccion.getValor(); i++){
	    		celdaAux = pila.pop();
	    		memoriaDatos[ (int)pila.peek().getValor() + i] = memoriaDatos[ (int)celdaAux.getValor() + i];
	    	}
	    	pila.pop();
	    }
	    
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.alloc)){
	    	int tam = (int) instruccion.getValor();
	    	for (int i = heapCounter; i < heapCounter + tam; i++){
	    		memoriaDatos[i] = new Celda(true); //reservada = true
	    	}
	    	pila.push( new Celda(Tipo.INT, heapCounter) );
	    	heapCounter += tam;
	    }
	    
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.free)){
	    	int d = (int) pila.pop().getValor();
	    	int tam = (int) instruccion.getValor();
	    	for (int i = d; i < d + tam; i++){
	    		memoriaDatos[i] = null; //reservada = true
	    	}
	    }	
	    
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.ir_f)){
	    	celdaAux = pila.pop();
	    	if ( (int)celdaAux.getValor() == 0 ){
	    		contadorPrograma = (int) instruccion.getValor() - 1;
	    	}
	    }
	    
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.ir_a)){
	    	contadorPrograma = (int) instruccion.getValor();
	    }
	    
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.ir_ind)){
	    	contadorPrograma = (int) pila.peek().getValor() - 1;
	    	pila.pop();
	    }	
	    
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.copia)){
	    	celdaAux = new Celda(pila.peek());
	    	pila.push(celdaAux);
	    }
	    
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.desapila)){
	    	pila.pop();
	    }
	    
	    /*
	    //Optimizaciion: Desapilamos toda la pila cuando llegamos a un PyC
	    else if(instruccion.getInstruccion().equals(TipoInstruccion.desapilaTam)){
	    	for(int i = 0; i <= (int)instruccion.getDir(); i++){
	    		pila.pop();
	    		cimaPila = -1;
	    	}
	    }
	    */
	}
	
	public void muestraEstado() throws IOException{
		System.out.println("****************************");
		System.out.println("\nContenido de la pila de ejecucion:");
		if(cimaPila == -1)System.out.println("Pila vacia.");
		else{
			System.out.println("[" + cimaPila + "]" + pila.get(cimaPila).toString() + " <--- CIMA");
			for (int i = cimaPila; i>0;i--){
				System.out.println("[" + (i-1) + "]" + pila.get(i-1).toString());
			}
		}
		System.out.println("\nContenido de las celdas de memoriaDatos:");
		int i = 0;
		if(memoriaDatos[0] == null) System.out.println("Memoria sin datos.");
		while(memoriaDatos[i] != null){
			System.out.println("[" + i + "]" + memoriaDatos[i].toString());
			i++;
		}
		System.out.println("\n****************************");
	}
	
	public static void main(String args[]) throws IOException, ClassNotFoundException{
		if (args.length == 2){
			CInstruccionIO io = new CInstruccionIO();
			List<CInstruccion> is = io.leerInstrucciones(args[0]);
			MaquinaP mp = new MaquinaP(is, Boolean.parseBoolean(args[1]));
			mp.run();
		}
		else System.out.println("Forma de uso: MaquinaP.java nombreFicheroObjecto _modoTraza");
	}
}
